package com.tutu.mybatis.dynamic.datasource.service.impl;

import com.tutu.mybatis.dynamic.datasource.config.DynamicRoutingDataSource;
import com.tutu.mybatis.dynamic.datasource.config.DynamicRoutingDataSourceHolder;
import com.tutu.mybatis.dynamic.datasource.config.DynamicSourceConfig;
import com.tutu.mybatis.dynamic.datasource.bean.DatasourceObj;
import com.tutu.mybatis.dynamic.datasource.service.DatasourceManage;
import com.tutu.mybatis.dynamic.datasource.util.ApplicationContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author qyy
 * @date 2021/3/18 15:12
 */
@Service
public class DatasourceManageImpl implements DatasourceManage {

    private static final Logger logger = LoggerFactory.getLogger(DatasourceManageImpl.class);

    @Resource
    private DynamicRoutingDataSource dynamicRoutingDataSource;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void addDatasource(DatasourceObj datasourceObj) {
        String datasourceKey = DynamicRoutingDataSourceHolder.buildKey(datasourceObj.getUserId());
        ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) ApplicationContextHolder.applicationContext;
        lock.lock();
        try {
            logger.info("尝试从IOC容器中获取数据源:{}", datasourceKey);
            applicationContext.getBean(datasourceKey, DataSource.class);
        } catch (NoSuchBeanDefinitionException e) {
            logger.info("从IOC容器中获取数据源:{} 失败", datasourceKey);
            //这是一个unmodifiableMap
            Map<Object, DataSource> dataSources = dynamicRoutingDataSource.getResolvedDataSources();
            Map<Object, DataSource> map = new HashMap<>(dataSources);
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
            logger.info("构建新数据源:{}", datasourceKey);
            BeanDefinitionBuilder beanDefinitionBuilder = DynamicSourceConfig.buildDatasource(datasourceObj.getUserId(), datasourceObj.getUsername(),
                    datasourceObj.getPassword(),
                    datasourceObj.getUrl());
            logger.info("将新数据源注册到IOC容器:{}", datasourceKey);
            beanFactory.registerBeanDefinition(datasourceKey, beanDefinitionBuilder.getRawBeanDefinition());
            map.put(datasourceKey, applicationContext.getBean(datasourceKey, DataSource.class));
            logger.info("再将所有数据源交给mybatis");
            dynamicRoutingDataSource.setTargetDataSources(Collections.unmodifiableMap(map));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public String removeDatasource(Long userId) throws NoSuchBeanDefinitionException {
        String datasourceKey = DynamicRoutingDataSourceHolder.buildKey(userId);
        ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) ApplicationContextHolder.applicationContext;
        lock.lock();
        try {
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
            beanFactory.removeBeanDefinition(datasourceKey);
            Map<Object, DataSource> dataSources = dynamicRoutingDataSource.getResolvedDataSources();
            Map<Object, DataSource> map = new HashMap<>(dataSources);
            map.remove(datasourceKey);
            dynamicRoutingDataSource.setTargetDataSources(Collections.unmodifiableMap(map));
        } catch (NoSuchBeanDefinitionException e) {
            logger.error("{}数据源不存在", datasourceKey);
            throw e;
        } finally {
            lock.unlock();
        }
        return datasourceKey;
    }
}
